OS - Lab2实验报告
归档于2025年7月8日。
杂记

我们需要回顾一下内存空间相关的知识。
kuseg:用户态空间,需要通过TLB和Cachekseg0:过Cache,不过TLB;MMU拉低最高位(-0x8000_0000)得物理地址kseg1:Cache和TLB都不过;MMU拉低高三位得物理地址kseg2:过TLB和Cache
还有,MIPS通过MMIO (Memory Mapped IO)进行外设访问。显然外设处在固定的物理地址上,我们肯定想到kseg1。
TLB需要软件初始化,故内核最开始自然放在不用TLB的kseg0上。kseg1暂时认为是访问IO设备专用。
思考题
2.1
指针变量存储的一般认为是虚拟地址。
在我们实现了地址翻译功能的硬件上,lw,sw访问的地址被视为虚拟内存;而像我们计组课设中直接访问内存的处理器核上,被视为物理地址。
2.2
C语言本身没有泛型语法,这样写可以做到创建一个泛型双向链表,提高可重用性。这同时也省掉了重复造轮子的烦恼,不是吗?(笑)
对于单项链表:插入只需更新两个next域(如insert_after,则是更新当前的,与待插入的两个节点),效率优于剩余二者(循环链表,双向链表还需维护prev);但在删除时,单项列表需要遍历,效率较低;
对于循环列表:可以认为是双向链表的特殊情况;操作次数与双向列表近乎一致,不过由于需要特判头部节点,以及
prev指针需要解引用来维护,效率可能较双向列表略有下降。
2.3
- C。按照
queue.h展开即可。
2.4
查询4Kc手册P41,我们可以得知ASID的定义:

类似于我们使用PID区分不同的进程,我们使用ASID进行不同进程/线程对应内存空间的标识。 不同的进程,其内存空间/上下文一般情况下不同,虚拟地址到物理地址的映射一般也会不同。这就需要ASID,指示TLB中的映射是否有效。
ASID有8位,对应即是2^8 = 256个不同的地址空间。
2.5
tlb_invalidate()调用tlb_out。- 无效化
ASID与虚拟地址va对应的页表项;若页表项不存在,则什么都不做。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
273 LEAF(tlb_out) // LEAF函数,不调用任何其他子例程,不使用栈上的任何内存空间
4 .set noreorder // 指定下面程序不可交换执行顺序
5 mfc0 t0, CP0_ENTRYHI // 暂存当前的ENTRYHI
6 mtc0 a0, CP0_ENTRYHI // 将传入的参数(VPN,ASID)写入ENTRYHI
7 nop // 消除冲突
8 /* Step 1: Use 'tlbp' to probe TLB entry */
9 /* Exercise 2.8: Your code here. (1/2) */
10 tlbp // 从ENTRYHI指导的VPN和ASID,将TLB表项号读入INDEX
11 nop
12 /* Step 2: Fetch the probe result from CP0.Index */
13 mfc0 t1, CP0_INDEX // 将INDEX内容提取出来
14 .set reorder // 允许编译器调换执行顺序,因为下面程序对结果没影响
15 bltz t1, NO_SUCH_ENTRY // 当INDEX最高位为1时,表项无效,比较时认为$t1 < 0
16 .set noreorder // 不允许编译器调换执行顺序
17 mtc0 zero, CP0_ENTRYHI // 准备清空内容
18 mtc0 zero, CP0_ENTRYLO0 // 准备清空内容
19 mtc0 zero, CP0_ENTRYLO1 // 准备清空内容;由于奇偶页设计,我们需要LO0,LO1都准备上
20 nop
21 /* Step 3: Use 'tlbwi' to write CP0.EntryHi/Lo into TLB at CP0.Index */
22 /* Exercise 2.8: Your code here. (2/2) */
23 tlbwi // 清空INDEX对应的TLB表项
24 .set reorder
25
26 NO_SUCH_ENTRY: // 无论表项是否有效,下面程序我们都要执行
27 mtc0 t0, CP0_ENTRYHI // 恢复之前暂存的ENTRYHI
28 j ra // 函数执行完成,返回原程序
29 END(tlb_out)
2.6
整体来说,用户程序进行CPU缓存的过程是这样:
- 执行用户程序前,由内核进行TLB等内存相关硬件的初始化,随后再执行用户程序
- 用户程序执行到
lw/sw指令,检查TLB - (我们先不考虑Cache)若TLB表项命中,则将处理器中的虚拟地址转换为物理地址,进行访存
- 若TLB未命中,则由硬件产生缺页中断,进入内核态,执行对应的异常处理程序;
- 在异常处理程序中,调用了我们Lab中实现的
do_tlb_refill等一系列函数 - 执行完成后,从内核态退出,回到产生异常的访存指令,继续执行。
2.7
我选择LoongArch;此处仅对32位的LA32R进行讨论。
由于一些历史渊源,LoongArch有不少地方跟MIPS十分相似,比如,页表都采用了奇偶页的设计:

在我们本单元介绍的4Kc核中,MMU必须通过TLB进行地址翻译,而LA32R可以通过CSR(控制状态寄存器)配置地址翻译模式,在MMU处实现更灵活的地址翻译:
![[Pasted image 20250401224956.png]]
直接地址翻译模式
在地址为32位时,最高位拉低。类似于我们在MIPS中处理kseg0的虚拟地址转换。
直接映射地址翻译模式
将虚拟地址的[31:29]位,由CSR中存储的映射,进行变换。仅在虚拟地址的[31:29]位命中时,且权限允许,才允许翻译。


这跟我们在MIPS中kseg1拉低高三位地址,得到实际地址有一点像,不过这里允许的映射更灵活。
页表映射翻译模式
整体思路与MIPS实现基本一致,不过LA32R中由于权限等级不止内核态/用户态,还会对权限进行检查。同时也正因此在内的一些其它原因,LA32R中TLB表项结构与MIPS略有差距。

在Tag项中,LA32R多放了一个
E,指示TLB表项是否为空;Data项的V仍然执行指示TLB项有效的功能。PS仅在MTLB(数据存储对应的TLB)中出现,指示页的大小(LA32R支持大页)PLV0/1:指示特权等级限制。
MAT:指示存储访问类型;具体有哪些访问模式见下,与MIPS下的基本一致。
LA32R了采取两种地址翻译模式;在映射地址翻译模式下,访问模式存在TLB表项中(这一点跟MIPS TLB项中的C0/C1域相似);而在直接地址翻译模式下,由于不通过TLB,我们必须想办法存这一情况的访存模式,这就是
CSR.CRMD中存直接翻译情况下访存模式的目的。其中,
CSR.CRMD指示了当前所处的特权等级,中断使能与(直接地址翻译模式时的)地址翻译模式:

其余过程与MIPS是基本一致的,不过:
- TLB访问/维护过程直接被打包成了一条指令
- CSR中内容的划分与MIPS的CP0不同,比如LA32R中的ASID单独使用一寄存器管理。
限于篇幅,且并不紧要,这就不再展开了。
Appendix A.1
整体布局应该是这样的:
1 | [PD] - [PD] - [PD] - [PD] - [PD] - [PD] - [UPD] - [PD] |
其中,
1 | U:代指Unique |
- 由于题意语焉不详,我们认为所求的是三级页表的一级页表。
1
addr_UPD = PT_base + PT_base >> 30 << 21
1
result = PT_base + PT_base >> 30 << 21 + PT_base >> 30 << 12 + PT_base >> 30 << 3
难点分析
双向链表的实现
主要是双向链表的实现并不直观,需要花时间进行理解。
整理下来,双向链表的结构整体长这样:

- 头节点特殊,只有
next域 - 对于一个节点,有
prev,next两个域 next是指向下一节点的指针prev是 指向上一节点的next指针 的指针- 由此设计,可以避免对头节点的特判。因为我们维护头节点的
next,只需解下一个节点的prev指针,就可维护。
页的管理
我们在整个Lab里做的,都是页控制块;
关键在于mips_vm_init()这个函数:
这里面涉及的都是物理页面/物理页号。
pages的作用,不仅是记录页的基地址,以通过&pages[i]的方式访问第i页的地址,还有以pages[i]的形式,访问与维护第i个控制块。
转换到物理地址,就需要指导书中提到的两个宏了:
而虚拟地址到物理地址的映射,是由page_insert完成的。

实验体会
本周完成耗时远高于Lab1:
- 一是内存管理具有一定的复杂度;
- 二是第一次接触一些实现,如本实验中的双向列表,理解需要一定的时间;
- 思考题深度很深。
要顺利完成的话,必须对内存管理机制足够熟悉才行……
原创说明
除引用的手册内容以外,本报告为本人原创。
OS - Lab2实验报告